Skip to content

feat(word): add tracked changes find/replace support#71

Open
kuishou68 wants to merge 674 commits into
iOfficeAI:mainfrom
kuishou68:upstream/tracked-changes-core
Open

feat(word): add tracked changes find/replace support#71
kuishou68 wants to merge 674 commits into
iOfficeAI:mainfrom
kuishou68:upstream/tracked-changes-core

Conversation

@kuishou68

Copy link
Copy Markdown
Contributor

Summary

  • add tracked=true support to Word find/replace so matches are emitted as w:del + w:ins
  • add trackRevisions / trackChanges setting support to explicitly toggle review mode in settings.xml
  • keep the change scoped to Word set/doc-settings paths without touching preview/runtime behavior

Why

This moves tracked review markup generation into OfficeCLI's OpenXML flow instead of requiring callers to patch DOCX ZIP/XML externally. It also makes it possible to exit review mode cleanly after acceptAllChanges / rejectAllChanges.

Validation

  • dotnet restore src/officecli/officecli.csproj -nologo
  • dotnet build src/officecli/officecli.csproj -nologo --no-restore

goworm added 30 commits April 15, 2026 00:30
Closes 4 CodeQL js/incomplete-sanitization alerts (lines 58, 64, 119,
121). Aligns with the existing pattern at lines 73, 94, 379.
Refactor Excel row sort to follow the region-action convention used by
merge: the sort range is now encoded in the path (/Sheet1/A1:C100) rather
than a separate --prop range=... Sheet-level path auto-detects the used
range and delegates to the same SortRangeRows helper.

Correctness fixes folded into the rewrite:
- precise column-letter match (old StartsWith('A') misfired on AA)
- raw CellValue comparison (not display-formatted text) so numeric keys
  compare as doubles even when the cell has a format code
- first sort key uses OrderBy (was ThenBy on a no-op identity)
- per-row sort-key materialization (was O(rows × keys × cells/row))
- reject ranges intersecting merged cells (was silent corruption)
- reject ranges with shared-formula groups (was broken ref anchors)
- sortState placed after SheetData; sortCondition@ref scoped to the key
  column within the sort range

New sheet-level contract:
  set data.xlsx /Sheet1/A1:C100 --prop sort='A asc, B desc' --prop sortHeader=true
  set data.xlsx /Sheet1        --prop sort='A asc'         (auto-detect)
Repro: set xxx.xlsx '/Sheet1/A1:A3' --prop sort=''
Before: silently succeeded, no sort applied, no error.
After: throws ArgumentException 'sort value cannot be empty'.

Sheet-level sort='' keeps its clear-sortState semantics (handled by the
sheet-level dispatcher before reaching SortRangeRows); the throw fires only
when the empty value arrives via a range path.
Repro: set xxx.xlsx '/Sheet1/A1:B10' --prop sort='C asc'
Before: silently succeeded with no reorder and wrote a malformed
sortCondition ref pointing outside sortState@ref.
After: throws ArgumentException 'Sort column C is outside the range A:B'.

The check runs after each key is parsed in SortRangeRows, using the
normalized col1/col2 bounds.
Repro: set xxx.xlsx '/Sheet1/A3:A1' --prop sort='A asc'
Before: row1>row2 scan produced an empty rowsInRange, data was not
reordered, and sortState@ref was written as the literal reversed 'A3:A1'.
After: SortRangeRows swaps col1/col2 and row1/row2 when they arrive in
max:min order. Rows reorder correctly and sortState@ref is well-formed.
Repro: set xxx.xlsx '/Sheet1/A1:B10' --prop 'sort=A asc B'
Before: parsed as (A, asc) and silently dropped the trailing 'B'.
After: throws ArgumentException 'Invalid sort key ...: too many tokens.
Expected <col> [asc|desc]'.

Applies per comma-separated key entry, so 'A asc, B desc extra' also
fails loudly on the second key.
Before: WriteSortState always did sheetData.InsertAfterSelf(sortState).
When an autoFilter was present on the sheet, this produced the child
order [sheetData, sortState, autoFilter], which violates the
CT_Worksheet schema (autoFilter must precede sortState).

After: if an autoFilter child exists, insert sortState immediately
after it; otherwise keep the existing 'after sheetData' placement.
Repro:
  set A1=30 A2=10 B1==A1+1000 B2==A2+1000
  set A1:B2 sort='A asc'
  -> B1 silently became =A2+1000 (stale ref to old row)

Cause: SortRangeRows rewrites Cell.CellReference to the new row index
but leaves CellFormula.Text encoding the *old* relative addresses, so
Excel recalculates against wrong refs and silently produces wrong
values. Data-corruption class.

Fix: extend the existing shared-formula reject to cover any cell with
a CellFormula in the data rows (CONSISTENCY(sort-rejects-formulas)).
A full ref-rewrite (handling A1/\$A\$1/A:B/Sheet!A1/named ranges) is
high risk for partial-correctness regressions and deferred to v2.

Known limitation: does not catch formulas *outside* the sort range
that reference cells *inside* it; same scope as the shared-formula
check.
Repro: a sheet where SheetData lists rows out of RowIndex order
(e.g. <row r="3">, <row r="1">, <row r="2">) — legitimate output from
some writers or malformed edits. SortRangeRows built originalIndices
from List position (document order), so the sorted data was mapped
onto scrambled target row numbers, producing a wrong arrangement.

Fix: OrderBy(v => v) on originalIndices so sorted slots are always
assigned in ascending row order regardless of SheetData layout.
Repro: a sheet containing mergeCells but no autoFilter. After a sort,
WriteSortState fell back to sheetData.InsertAfterSelf(ss), producing
a child sequence of sheetData → sortState → mergeCells which violates
CT_Worksheet (sortState must precede mergeCells? No — sortState sits
AFTER autoFilter and BEFORE mergeCells per ECMA-376). Strict
validators reject the document; Excel silently ignores the sortState.

Fix: thread sortState into its correct schema slot by walking the
predecessors in reverse (autoFilter, scenarios, protectedRanges,
sheetProtection, sheetCalcPr, sheetData) and InsertAfterSelf the
nearest present anchor. This places sortState between its nearest
predecessor and any successor (mergeCells, conditionalFormatting,
hyperlinks, etc.).
…plicates)

Repro: two related malformed inputs that silently corrupted sort output.
  (a) A <row> element without a RowIndex attribute — the range filter
      `r.RowIndex?.Value >= dataStartRow` silently dropped that row, so
      the row's data survived sort but was reassigned a stale RowIndex,
      losing the user's data alignment.
  (b) Two <row r="N"> entries with the same RowIndex — sort wrote two
      rows into the same target slot, silently dropping one.

Fix: at SortRangeRows entry, scan SheetData once and throw
InvalidOperationException on either condition. Sorting a corrupted
layout should surface the corruption, not silently paper over it.
…t=false

Repro: a sheet with sheetProtection@sheet="true" could still be sorted
silently, mutating a worksheet the author explicitly protected. The
sort also leaves the protection in place, so the user's next interactive
sort in Excel will be blocked — masking the breach.

Fix: at SortRangeRows entry, check the sheet's SheetProtection. If
@sheet is true and @sort is absent/true (OOXML default: sort IS
protected), throw InvalidOperationException. Honor the escape hatch
@sort="false", which per spec means "sort is excluded from the
protected operation set" — allow the sort in that case so we do not
regress legitimate workflows.
Replace per-cell inset box-shadow with a single absolutely-positioned
overlay div sized to the union rect of the selected cells. The previous
approach drew the selection frame via inset box-shadow, which rendered
visibly offset from the cell's visual edge in border-collapse tables
because adjacent cells share a 1px border and shadow positioning is
relative to the padding box, not the shared border edge.

The overlay anchors inside the table so it scrolls with content
automatically; a scroll/resize listener handles edge cases.
Previously sort refused the operation when any formula lived anywhere on
a row that overlapped the sort range, and when any row in the whole
sheet had a duplicate RowIndex. Both checks were over-broad:

- Formulas in columns outside the sort column range are unaffected by
  sort (the formula text and its refs stay intact even if the row moves).
- Duplicate RowIndex rows outside the sort row range cannot cause the
  sort step to lose or misplace data.

Narrow both checks to cells/rows that actually intersect the sort
range. Missing RowIndex is still always rejected because such a row
cannot be located in any range and risks silent drop by the sort scan.
…ll refs

ParseCellReference previously used int.Parse on the row portion of a
cell reference, which threw OverflowException on malformed inputs like
"A4294967295" (uint.MaxValue). The overflow bubbled all the way up as
an unhandled numeric exception with no document context.

Switch to long.TryParse and fold the range check into the same branch
so any row outside 1..1048576 — whether out-of-int-range or merely
out-of-Excel-range — produces a consistent ArgumentException with the
offending reference included.
double.TryParse("NaN") returns true, producing rank=0 (number), while
double.TryParse("1e999") overflows to +Infinity — also rank=0. The
resulting sort order mixed non-finite doubles with finite numbers in
ways Excel never does; Excel treats NaN / Infinity / -Infinity as
literal strings.

Classify those tokens (and any non-finite parse result) as rank=1
(string) so number/text ordering stays consistent with Excel.
The sheet-properties help block listed 'sort' but not the companion
'sortHeader' flag, even though the Set handler has consumed it since
sort landed. Add a one-line description next to sort.
When a caller passes both merge=true and sort=... to set /Sheet1/A1:B3,
merge was applied first and wrote MergeCells into the XML, then
SortRangeRows rejected the merged region and threw, leaving the file in a
half-written state with an unwanted merge persisted.

Detect the combo at SetRange entry and throw before any write. Users who
need both must split the call. Consistent with the existing
'fail-before-write' precedent (merged-cell reject, formula reject).
…Validations)

Row sort rewrote cell CellReference values in <sheetData>, but left
sidecar metadata untouched. Hyperlinks, comments, and single-cell
dataValidation sqref tokens continued to point at the old row positions —
so after sort the hyperlink/comment/validation appeared attached to a
different row of data.

Capture the old->new row mapping before mutating row indices, then rewrite
hyperlink ref, comment ref, and each single-cell token in dataValidation
sqref that falls inside the sort rectangle. Refs outside the rectangle
and multi-cell range tokens (e.g. A2:A10) that cross the sort boundary
are intentionally left untouched — splitting partial ranges would require
a more invasive rewrite.

Also rename the internal CellInRange helper to CellColumnInSortRange.
The name now accurately reflects that the check is column-only; row
containment is enforced by the caller iterating rowsInRange.
ConditionalFormatting rules anchored on single cells (e.g. a highlight
rule on A2:A2) were left pointing at the pre-sort cell after sort,
so the rule followed a row that no longer existed there. Extend the
post-sort sidecar rewrite with a ConditionalFormatting branch that
mirrors the dataValidations handling: tokenize sqref, skip multi-cell
range tokens (same partial-rect scope limitation), and remap each
single-cell token inside the sort rectangle via oldToNewRow.
Sorting A1:A1 or a range whose data region collapses to zero/one
rows is a logical no-op — there is nothing to reorder. The previous
code still wrote sortState in those branches, which made Excel UI
show a sort indicator on a range that was never actually sorted.
Skip WriteSortState in the two no-op paths so the UI stays honest.
Previously shown as A:asc,B:desc which fails at parse time — actual
sort spec is space-separated column+direction, comma-separated for
multi-key (e.g. 'Salary desc' or 'Dept asc, Salary desc'). AI agents
following the wrong example hit errors on every sort call.
RewriteSidecarRefsAfterSort handled hyperlinks, comments, threaded
comments, dataValidations, and conditionalFormatting but ignored
DrawingsPart. Pictures, shapes, and charts anchored at a row inside
the sort range stayed pinned to the original 0-indexed RowId after
the data under them was reordered, leaving them visually attached
to the wrong content row.

Now TwoCellAnchor and OneCellAnchor FromMarker/ToMarker RowIds are
remapped through oldToNewRow alongside the other sidecars. Follows
the same partial-rect scoping as dataValidations / conditional
formatting: a TwoCellAnchor is remapped only when both From and To
rows fall inside the sort rectangle; if the anchor straddles the
boundary it is preserved verbatim. OneCellAnchor has only From, so
it moves whenever From is inside. Columns are never rewritten
because sort only permutes rows.

Limitation: anchors straddling the sort rect boundary remain
authored-as-is, consistent with how multi-cell dataValidation and
CF range tokens are handled.
…e sortState

R7-1: physical sort comparer switched from Ordinal to OrdinalIgnoreCase so
mixed-case keys ("Apple"/"apple") land in an order consistent with the
sortState@caseSensitive=false metadata default and with Excel's own default.

R7-2: RewriteSidecarRefsAfterSort now also rewrites ProtectedRange sqref
(7th sidecar, same cell-anchored scoping as dataValidations / CF). Single-
cell tokens inside the sort rectangle follow row movement; range tokens
and out-of-rect tokens are preserved.

R7-3: all three SortState removal sites (sheet-level clear, range-level
clear, WriteSortState) iterate Elements<SortState>().ToList() instead of
GetFirstChild, so malformed files carrying duplicate sortState children
are fully collapsed to a single (or zero) element.

R7-4 (sortHeader default) rejected again with a CONSISTENCY(sort-header-default)
comment block at the dispatch site documenting the decision history and the
preferred future path (project-wide default flip, not a per-call heuristic
warning).
…leanup

- Sheet-level sort case now calls DeleteCalcChainIfPresent, matching the
  range-level sort path. Without this, a stale calc chain could survive
  the reorder and expose Excel to a mid-state repair risk on open.
- Swap ws.Elements<SortState>() -> ws.Descendants<SortState>() in the
  three sort-rewrite/clear sites so malformed files that nest <sortState>
  under <sheetData> are also cleaned on rewrite, instead of leaving the
  nested one behind and ending with two sortStates.
Add OOXML-compliant dual representation for SVG images:
- main a:blip/@r:embed → PNG fallback part (auto or user-supplied)
- a:blip/a:extLst asvg:svgBlip → SVG image part

Modern Office (2016+) renders the SVG; older viewers see the raster
fallback. Introduces Core/SvgImageHelper + SVG dimension parsing in
ImageSource so width/height auto-sizing matches PNG/JPEG behavior.

Supports 'fallback=<path>' prop on Add to override the 1x1 transparent
PNG default. Set (path/src) symmetrically strips/attaches the extension
and deletes the orphaned SVG part when replacing across formats.
When user passes a header name like 'Score' as a sort column, the prior
error wording ('Sort column SCORE is outside the range A:B') misled AI
agents into guessing column letters rather than recognizing that header
names are unsupported. Detect column tokens that parse past XFD with
length >= 4 and return a targeted 'Column names are not supported; use
column letters (A, B, AA, up to XFD)' message. Genuine out-of-range
letters (e.g. Z in A:B) still return the original range-error wording.
goworm and others added 28 commits April 19, 2026 02:54
GetShapeNode now reads <a:xfrm rot/flipH/flipV> on the shape's Transform2D
and surfaces them as format.rotation (canonicalized to 0-360 degrees) and
format.flip (h/v/both), matching how Add accepts these properties.
…els subkeys

Three small CLI-parity gaps surfaced across prior rounds.

Word underline= now accepts the full OOXML enum. NormalizeUnderlineValue
centralizes the mapping (wavy -> wave, dashdot -> dotDash, plus
double / thick / dotted / *Heavy variants / words). Previously the
handler rejected wavy despite PPTX accepting it.

Word firstLineIndent= accepts unit strings (2cm, 0.5in, 18pt) via
SpacingConverter.ParseWordSpacing. Bare twips still work (backward
compat). Range check > 31680 twips preserved.

Chart datalabels.* sub-keys (showvalue, showpercent, showcatname,
showsername, showlegendkey) now emit the corresponding c:show*
elements. Registered in DeferredAddKeys so they apply at Add time,
not only via post-build Set. ChartHelper is shared across PPTX and
Excel so the fix lands on both sides.
Two CLI-surface gaps carried from earlier rounds.

Slide HeaderFooter toggle (R16 deferred): set /slide[N] now accepts
showFooter, showSlideNumber, showDate, showHeader. Each maps to the
corresponding <p:hf ftr/sldNum/dt/hdr> attribute on the slide element
(PowerPoint writes <p:hf> directly under <p:sld> when the per-slide
"Header & Footer" dialog toggles it, so we match).

Picture fill modes (R10 Cleanup-D): add picture --prop fill=... now
accepts stretch (default, non-regression), contain, cover, and tile.
contain/cover sniff the image's native pixel dimensions via
ImageSource.TryGetDimensions and compute <a:fillRect> insets (in
thousandths of a percent) — positive for letterbox, negative for
crop. tile emits <a:tile sx sy algn flip> with optional tilescale,
tilealign, tileflip sub-keys. Falls back to bare stretch when image
dimensions can't be sniffed.
Two CLI gaps carried from earlier rounds.

AddPlaceholder: `add /slide[N] --type placeholder --prop phType=...`
now creates a <p:sp> with <p:ph type="..."/> for any of the known
placeholder types (body, date, footer, slidenum, header, subtitle,
title, picture, chart, table, diagram, media, obj, clipart). Emits
an empty <p:spPr> so geometry and fonts inherit from the layout,
plus a minimal <p:txBody> that optionally prepopulates via a text=
property. Dispatch wired in Add.cs.

Group resize preserves scaling: when `set /slide[N]/group[M] width=`
or `height=` / `x=` / `y=` is applied and the group's
`TransformGroup.ChildExtents` / `ChildOffset` is null, snapshot the
current Extents / Offset into new ChildExtents / ChildOffset BEFORE
updating Extents / Offset. This establishes the ext≠chExt baseline
that PowerPoint needs to visibly compress the group's children
instead of resizing them 1:1 with the container.
…ml anchor

Three gaps remaining from R14.

Hyperlinks now accept tooltip= alongside link= on both run and shape
Add / Set. ApplyRunHyperlink and ApplyShapeHyperlink set the Tooltip
attribute on HyperlinkOnClick.

link= gains internal-jump support:
- slide[N] creates a slide-to-slide relationship and emits
  action="ppaction://hlinksldjump"
- firstslide / lastslide / nextslide / previousslide emit the
  corresponding ppaction://hlinkshowjump?jump=... (PowerPoint-native)
ReadRunHyperlinkUrl resolves both forms back to the original string.

HTML preview wraps shape-level hyperlinks in an <a> tag. Shape
hlinkClick now lives on nvSpPr/cNvPr (canonical shape location) in
addition to runs so the HTML renderer can detect it from the shape
tree; runs still carry the inline anchor from R14 so text stays
clickable inline. External URLs get a wrapping anchor with
rel="noopener" target="_blank"; cursor:pointer affordance added.
Internal slide-jump links are intentionally not wrapped (no
navigable href in a static HTML preview).
Excel refuses to open files containing cells whose text length exceeds
2^15-1 (0x800A03EC on save/open). Previously Add, Set, and CSV import
wrote oversized values silently, corrupting the file.

Add EnsureCellValueLength in ExcelHandler.Helpers.cs (internal const
MaxCellTextLength = 32767) and call it from the Add cell value path,
the Set cell value/text path, and the CSV/TSV import path. Rejection
is a clear ArgumentException naming the cell and the observed length.
Setting a cell to an ISO datetime like "2024-03-15T10:30:00" with
type=date previously stored the literal string, so Excel rendered it
as text instead of a real date. Only date-only ("2024-03-15") and
numeric serials were converted.

Centralize the format list as IsoDateFormats + TryParseIsoDateFlexible
in ExcelHandler.Helpers.cs, accepting yyyy-MM-dd, yyyy-MM-ddTHH:mm,
yyyy-MM-ddTHH:mm:ss, yyyy-MM-dd HH:mm:ss, and the ...Z / .fff[Z]
variants. Add and Set cell-value paths route through the shared helper
so every entry point converts to a fractional OADate.
When both value= and formula= are passed to Set or Add on the same
cell, formula wins (it is written last, clearing CellValue). Users who
typo'd or duplicated props previously had no indication the literal
value had been discarded.

Emit a stderr warning ("Both value= and formula= supplied — using
formula, value ignored.") from both the Add cell path and the Set
value/text path so the precedence is visible.
Excel silently drops effectLst children that violate DrawingML schema
order (blur → fillOverlay → glow → innerShdw → outerShdw → prstShdw →
reflection → softEdge). Previously shape Add appended outerShdw before
glow, so a shape with both shadow and glow rendered without the glow
halo despite being present in the raw XML.

Reorder the shape Add effect build to emit children in schema order,
and rewrite Set TrySetShapeEffect to InsertBefore the next-in-order
sibling when adding effects incrementally. Same fix applied to the
text-level effect block used by fill=none shapes.
Explicit type=string now forces an inline-string cell even when the
value is numeric-looking (e.g. value=123), instead of silently storing
a number. Explicit type=number with a non-numeric value now throws
ArgumentException instead of silently storing as string.
labelRotation= was previously accepted silently but dropped. Wire it
onto the target c:catAx / c:valAx via a <c:txPr>/<a:bodyPr rot="N"/>
where N is degrees * 60000 (OOXML 60000ths-of-a-degree encoding).
Accept bare labelRotation= (both axes) plus xAxis./valAxis./yAxis.
labelRotation aliases.
add shape --prop ref=B2 previously mapped to anchor=B2:B2 with
identical from/to markers, producing a zero-width/height invisible
shape in Excel. Expand single-cell refs to a 1-column x 1-row
rectangle (B2 -> B2:C3) so the shape has a visible extent. Range
refs (B2:D6) are unchanged.
conditional formatting rule=aboveAverage previously silently dropped
the stdDev= and equalAverage= properties. Both now flow onto the
cfRule attributes: stdDev=N (integer deviations from mean) and
equalAverage=true (include values equal to the mean). Also accepted
the aboveaverage= spelling as an alias for above= to mirror the
OOXML attribute name.
Set on slicer paths returned 'Element not found' because the Set
dispatch fell through to the generic XML fallback. Add a handler
that maps caption/style/rowHeight/columnCount/name onto the backing
X14.Slicer element via TryFindSlicerByIndex, mirroring the property
vocabulary exposed on Add.
Shape TextBody stores one <a:p> per paragraph; the Get readback
flattened all <a:r> descendants into a single string, so multi-line
text like 'Line1\nLine2\nLine3' was returned as 'Line1Line2Line3'.
Join runs within each paragraph, then join paragraphs with '\n' so
multi-line shape text round-trips through Set/Get.
When users pass series{N}.name=Sheet1!A1 the legend/tooltip text was
being written as literal <c:v>Sheet1!A1</c:v>, so Excel displayed the
string 'Sheet1!A1' instead of resolving it to the cell's value. Detect
cell-reference patterns in both Add (ApplySeriesReferences) and Set
(series.name case) and emit <c:tx><c:strRef><c:f>…</c:f></c:strRef></c:tx>
so Excel resolves the cell on open. Reader now also surfaces nameRef
alongside the existing valuesRef/categoriesRef format keys.
… formula pattern

Excel expects <x:tableColumn> to carry <x:calculatedColumnFormula> so
newly appended rows auto-fill. When Add builds a table over a range
where every data row in a column carries a formula with the same
relative shape (e.g. =J2*K2, =J3*K3 …), stamp the first formula onto
the tableColumn. Detection uses a naive strip-digits heuristic — it
matches how users actually author calc'd columns without trying to
reimplement Excel's relative-reference normalizer.
The formulacf dxf builder only threaded font.color and font.bold into
the dxf Font; font.underline, font.italic, font.strike, font.size,
font.name were silently dropped. Extract a BuildFormulaCfFont helper
that appends every sub-prop (b, i, u, strike, sz, name/FontName, color)
and share it from the formulacf Add path. Users who pass the full set
now get a dxf Font that Excel renders correctly.
When title (or axisTitle/catTitle) is passed a single-cell reference like
Sheet1!A1, emit <c:tx><c:strRef><c:f>Sheet1!$A$1</c:f></c:strRef></c:tx>
instead of a literal <a:t>Sheet1!A1</a:t>. Same fix family as the series
name strRef path — BuildChartTitle is shared by chart title and both axis
titles, so one call site fans out to all three.
freeze=A1 has colSplit=0 and rowSplit=0, which produced an invalid
<pane state="frozen" activePane="topRight"/> with no xSplit/ySplit.
Treat A1 as a no-op — any existing pane is already cleared earlier in
the branch, so simply break before emitting a new one.
When building a pivot from a source range, resolve each source column's
StyleIndex to its numFmtId and stamp it onto the cacheField. Without
this, a date-formatted column (numFmtId 164, yyyy-mm-dd) rendered in
the pivot as raw OADate serials (45306, 45337, ...) instead of the
intended date format. Reuses ResolveColumnNumFmtIds already used for
DataField.NumberFormatId.
The chart Set path accepted logBase=N and logScale=true (commit b5caba1)
but Add silently dropped yAxisScale/logScale/logBase because they were
handled only via the deferred-key path and yAxisScale wasn't registered.
Register yaxisscale as a deferred Add key and teach the logbase/logscale
setter case to accept 'log' / 'linear' as yAxisScale-style verbs. Add
now emits <c:logBase val="10"/> for yAxisScale=log or logScale=true,
and <c:logBase val="N"/> for logBase=N, matching Set.
SKILL.md (minimal additions per conciseness rule):
- Add `check` to L1 read commands
- Note SVG support on Word/Excel/PPT picture
- PPT: placeholder type, AddParagraph level/lineSpacing/spaceBefore/After
- Word: field types expanded (22 zero-param + 6 parameterized)
- Excel: pareto chart, CF types, anchor cell-range syntax, calculatedField

README.md:
- Excel features: sort (sheet/range/multi-key), pareto + log axis,
  SVG images, pivot calculatedField, _xlfn. auto-prefix
- PowerPoint features: slide HF toggles, pattern fill + blur,
  hyperlink tooltip + slide-jump, picture fill modes, placeholder add
- Word features: SVG images, expanded field types
- Command Reference: add `check` command
Text-overflow scanning now emits as DocumentIssue (Format+Warning, Id
prefix 'O') from ExcelHandler/PowerPointHandler ViewAsIssues, reusing
the existing CheckAllCellOverflow and CheckTextOverflow logic. The
standalone 'officecli check' command is removed — users migrate to
'officecli view <file> issues' (optionally '--type format').

Underlying CheckShapeTextOverflow/CheckCellOverflow/CheckAllCellOverflow
handler APIs are retained; they still back the inline overflow warning
emitted on add/set through the resident server.
Two issues (iOfficeAI#68):
1. MatchesRunSelector fell through to the generic XML attribute path for
   `color`, which returns the OOXML-stored value without `#`. Comparing
   against user input `#FF0000` always failed. Normalize both sides (strip
   `#`, upper-case) to align with the project Color Rules (Add/Set accept
   `#FF0000`, `FF0000`, named colors).
2. The table branch in Query only descended into cells for OLE and equation
   selectors; run selectors were skipped, so red runs inside tables were
   invisible to `query "run[color=#FF0000]"`. Added a run-selector branch
   that mirrors the existing OLE traversal, emitting
   /body/tbl[i]/tr[j]/tc[k]/p[m]/r[n] paths.
@kuishou68

Copy link
Copy Markdown
Contributor Author

Hi @kuishou68 — this PR has merge conflicts with the current main branch. The main branch has advanced significantly (2100+ commits) since this PR was created, and the Word handler files have been extensively refactored (split into domain partials, revision API redesign, etc.).

To resolve the conflicts, this branch needs a rebase against the latest main. The key files touched by this PR (WordHandler.Set.cs, WordHandler.Set.DocSettings.cs) have been restructured. The new WordHandler.Set.TrackedPatch.cs will need to be adapted to the new handler architecture.

Would you like me to attempt the rebase, or would you prefer to rework this on top of the latest main?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants